﻿using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using Infra.CustomException;
using Infra.Utils.ExpressionTree;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace DAL.Database.Db.EF
{
    public class Repository<T> : IRepository<T>
        where T : class, new()
    {
        #region Fields
        protected readonly DbContext _context;
        #endregion

        #region Properties
        private DbSet<T> _dbSet;
        protected DbSet<T> DbSet
        {
            get
            {
                if (this._dbSet == null)
                    this._dbSet = this._context.Set<T>();
                
                return this._dbSet;
            }
        }

        public IEnumerable<T> Get
        {
            get
            {
                return this.DbSet;
            }
        }

        public int Count
        {
            get
            {
                return this.DbSet.Count();
            }
        }
        #endregion

        #region Constructors
        public Repository(DbContext db)
        {
            this._context = db;
        }
        #endregion

        public virtual IQueryable<T> All()
        {
            return this.DbSet;
        }

        public IEnumerable<T> Filter(T entity, IQueryManager querymanager)
        {
            return this.DbSet.WhereAll(querymanager.Get(entity));
        }

        public virtual IQueryable<T> Filter(Expression<Func<T, bool>> predicate)
        {
            return this.DbSet.Where(predicate);
        }

        public virtual IQueryable<T> Filter(Expression<Func<T, bool>> filter, out int total, int index = 0, int size = 50)
        {
            var skipCount = index * size;

            var resultSet = (filter == null) ? this.DbSet : this.DbSet.Where(filter);
            total = resultSet.Count();
            resultSet = (skipCount == 0) ? resultSet.Take(size) : resultSet.Skip(skipCount).Take(size);

            return resultSet;
        }

        public virtual IEnumerable<T> Filter(T entity, IQueryManager querymanager, out int total, int index = 0, int size = 50)
        {
            var skipCount = index * size;

            var resultSet = (entity == null) ? this.DbSet : this.DbSet.WhereAll(querymanager.Get(entity));
            total = resultSet.Count();
            resultSet = (skipCount == 0) ? resultSet.Take(size) : resultSet.Skip(skipCount).Take(size);

            return resultSet;
        }

        public virtual bool Contains(Expression<Func<T, bool>>  predicate)
        {
            return this.DbSet.Any(predicate);
        }

        public virtual T Find(params object[] keys)
        {
            return this.DbSet.Find(keys);
        }

        public virtual T Find(T entity)
        {
            var keys = new List<object>();

            foreach (var property in typeof(T).GetProperties())
            {
                foreach (var attribute in property.GetCustomAttributes(false))
                {
                    if (attribute is KeyAttribute)
                        keys.Add(property.GetValue(entity));
                }
            }

            return this.Find(keys.ToArray());
        }

        public virtual T Find(Expression<Func<T, bool>> predicate)
        {
            return this.DbSet.FirstOrDefault(predicate);
        }

        public virtual T Insert(T entity)
        {
            return this.DbSet.Add(entity);
        }

        public virtual void Delete(T entity)
        {
            this.DbSet.Remove(entity);
        }

        public virtual void Delete(Expression<Func<T, bool>> predicate)
        {
            var result = this.Filter(predicate);

            foreach (var item in result)
            {
                this.Delete(item);
            }
        }

        public virtual void Update(T entity)
        {
            this._context.Entry(entity).State = EntityState.Modified;
        }

        public virtual void Update(Expression<Func<T, bool>> predicate, Action<T> action)
        {
            var result = this.Filter(predicate);

            foreach (var item in result)
            {
                action(item);
                this.Update(item);
            }
        }

        public virtual T InsertIfNotExist(T entity)
        {
            var result = this.Find(entity);

            if (result == null)
                result = this.Insert(entity);

            return result;
        }

        public virtual T InsertIfNotExist(Expression<Func<T, bool>> predicate, T entity)
        {
            var result = this.Find(predicate);

            if (result == null)
                result = this.Insert(entity);

            return result;
        }

        public T InsertOrUpdate(Expression<Func<T, bool>> predicate, T entity, Action<T> action)
        {
            if (this.Contains(predicate))
                this.Update(predicate, action);
            else
                return this.Insert(entity);

            return entity; 
        }

        public virtual void Restore(T entity)
        {
            //TODO
        }

        public virtual void SaveChanges()
        {
            try
            { 
                this._context.SaveChanges();
            }
            catch (Exception e)
            {
                throw new DalException(e);
            }
        }

        public virtual void Dispose()
        {
            if (this._context != null)
                this._context.Dispose();
        }
    }
}
